// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © PtGambler

//@version=5

// Based on the following amazing script:
//
// Study       : Volume Profile, Pivot Anchored
// Author      : © dgtrd
//

indicator("soco_VP_DGTRD", "soco_VP_DGTRD", true, max_bars_back = 500, max_boxes_count = 500)//, max_lines_count = 100, max_labels_count = 100)

priceTxt  = str.tostring(close, format.mintick)
tickerTxt = syminfo.ticker

// Functions  -----------------------------------------------------------------------------------    

f_resInMinutes() =>
    _resInMinutes = timeframe.multiplier * (timeframe.isseconds ? 1. / 60 : timeframe.isminutes ? 1. : timeframe.isdaily ? 60. * 24 : timeframe.isweekly ? 60. * 24 * 7 : timeframe.ismonthly ? 60. * 24 * 30.4375 : na)
    _resInMinutes

f_tfResInMinutes(_res) =>
    request.security(syminfo.tickerid, _res, f_resInMinutes())

f_tfIsIntraday(_res) =>
    [intraday, daily, weekly, monthly] = request.security(syminfo.tickerid, _res, [timeframe.isintraday, timeframe.isdaily, timeframe.isweekly, timeframe.ismonthly])
    check = intraday ? "Intraday" : daily ? "Daily" : weekly ? "Weekly" : monthly ? "Monthly" : "Error" 
    check

f_drawOnlyLineX(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width) =>
    id = line.new(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width)

f_drawLineX(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width) =>
    var id = line.new(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width)
    line.set_xy1(id, _x1, _y1)
    line.set_xy2(id, _x2, _y2)
    line.set_color(id, _color)
    id

f_drawOnlyBoxX(_left, _top, _right, _bottom, _border_color, _border_width, _border_style) =>
    box.new(_left, _top, _right, _bottom, _border_color, _border_width, _border_style, bgcolor=_border_color)

f_drawOnlyLabelX(_x, _y, _text, _xloc, _yloc, _color, _style, _textcolor, _size, _textalign, _tooltip) =>
    label.new(_x, _y, _text, _xloc, _yloc, _color, _style, _textcolor, _size, _textalign, _tooltip)

f_drawLabelX(_x, _y, _text, _xloc, _yloc, _color, _style, _textcolor, _size, _textalign, _tooltip) =>
    var id = label.new(_x, _y, _text, _xloc, _yloc, _color, _style, _textcolor, _size, _textalign, _tooltip)
    label.set_xy(id, _x, _y)
    label.set_text(id, _text)
    label.set_tooltip(id, _tooltip)

f_getHighLow(_len, _calc, _offset) =>
    if _calc
        htf_l = low [_offset]
        htf_h = high[_offset]
        vol   = 0.
        
        for x = 0 to _len - 1
            htf_l := math.min(low [_offset + x], htf_l)
            htf_h := math.max(high[_offset + x], htf_h)
            vol += volume[_offset + x]

        htf_l := math.min(low [_offset + _len], htf_l)
        htf_h := math.max(high[_offset + _len], htf_h)
        
        [htf_h, htf_l, vol]

f_checkBreaches(arrayOfLines, extend) =>
    int qtyOfLines = array.size(arrayOfLines)
    for lineNo = 0 to (qtyOfLines > 0 ? qtyOfLines - 1 : na)
        if lineNo < array.size(arrayOfLines)
            line  currentLine   = array.get(arrayOfLines, lineNo)
            float lineLevel     = line.get_y1(currentLine)
            bool  lineWasCrossed = math.sign(close[1] - lineLevel) != math.sign(close - lineLevel)
            bool  lineWasTouched = math.sign(close[1] - lineLevel) != math.sign(low - lineLevel) or math.sign(close[1] - lineLevel) != math.sign(high - lineLevel) 

            if lineWasCrossed and extend == 'Until Bar Cross'
                array.remove(arrayOfLines, lineNo)
                // int(na)
            else if lineWasTouched and extend == 'Until Bar Touch'
                array.remove(arrayOfLines, lineNo)
                // int(na)
            else
                line.set_x2(currentLine, bar_index)
                // int(na)
    int(na)
 
f_checkBreaches_alert(arrayOfLines, extend) =>
    poc_alert_id = 0
    int qtyOfLines = array.size(arrayOfLines)
    for lineNo = 0 to (qtyOfLines > 0 ? qtyOfLines - 1 : na)
        if lineNo < array.size(arrayOfLines)
            line  currentLine   = array.get(arrayOfLines, lineNo)
            float lineLevel     = line.get_y1(currentLine)
            bool  lineWasCrossed = math.sign(close[1] - lineLevel) != math.sign(close - lineLevel)
            bool  lineWasTouched = math.sign(close[1] - lineLevel) != math.sign(low - lineLevel) or math.sign(close[1] - lineLevel) != math.sign(high - lineLevel) 

            if lineWasCrossed  //and boxNo != qtyOfBoxes - 1
                poc_alert_id := 1
            else if lineWasTouched //and boxNo != qtyOfBoxes - 1
                poc_alert_id := 2

        poc_alert_id

// Inputs ---------------------------------------------------------------------------------------

group_volume_profile    = 'Periodic Volume Profile'

periodic_tf       = input.timeframe("D", "Periodic Timeframe", group = group_volume_profile, tooltip= 'Note that some higher timeframe may not work properly due to maximum historical bars limitation')
regular_sess      = input.bool(true, 'Profile starts on Regular Session (For 1D Periodic Timeframe Only)', group = group_volume_profile, tooltip = 'For tickers displaying Extended Trading Hours, and for 1D Periodic Timeframe only')

tooltip_vp        = 'Volume Profile - displays total trading activity over a specified time period at specific price levels'
volumeProfile     = input.bool(true, 'Volume Profile (Common Interest)', inline='BB3', group = group_volume_profile, tooltip = tooltip_vp)
totalVolumeColor  = input.color(color.new(#2a2e39, 45), '' , inline='BB3', group = group_volume_profile)
vaVolumeColor     = input.color(color.new(#2a2e39, 67), '' , inline='BB3', group = group_volume_profile)

tooltip_va        = 'Value Area (VA) – The range of price levels in which a specified percentage of all volume was traded during the time period'
isValueArea       = input.float(70, "Value Area Volume %", minval = 0, maxval = 100               , group = group_volume_profile, tooltip = tooltip_va) / 100
profileLevels     = input.int(72, 'Number of Rows' , minval = 10, maxval = 100 , step = 1         , group = group_volume_profile)
profilePlacement  = input.string('Right', 'Placment', options = ['Right', 'Left']                  , group = group_volume_profile)
profileWidth      = input.int(10, 'Profile Width %', minval = 0, maxval = 100                     , group = group_volume_profile) / 100

tooltip_poc       = 'Point of Control (POC) - The price level for the time period with the highest traded volume'
pointOfControl    = input.bool(true, 'Point of Control (PoC)'                       , inline='PoC', group = group_volume_profile, tooltip = tooltip_poc)
pocColor          = input.color(color.new(#434651, 45), ''                       , inline='PoC', group = group_volume_profile)
pocExtend         = input.string('None', 'Extend Point of Control (PoC)', options=['Until Last Bar', 'Until Bar Cross', 'Until Bar Touch', 'None'], group = group_volume_profile)
pocWidth          = input(2, title= 'PocWidth', inline='PoC')

tooltip_vah       = 'Value Area High (VAH) - The highest price level within the value area'
valueAreaHigh     = input.bool(true, 'Value Area High (VAH)'                        , inline='VAH', group = group_volume_profile, tooltip = tooltip_vah)
vahColor          = input.color(color.new(#2a2e39, 45), ''                      , inline='VAH', group = group_volume_profile)

tooltip_val       = 'Value Area Low (VAL) - The lowest price level within the value area'
valueAreaLow      = input.bool(true, 'Value Area Low (VAL) '                        , inline='VAL', group = group_volume_profile, tooltip = tooltip_val)
valColor          = input.color(color.new(#2a2e39, 45), ''                      , inline='VAL', group = group_volume_profile)
vaWidth           = input(2, title= 'VaWidth', inline='PoC')

vaBackground      = input.bool(true, 'Background Fill of Value Area (VA)'           , inline='vBG', group = group_volume_profile)
vaBackgroundColor = input.color(color.new(#2a2e39, 89), ''                     , inline='vBG', group = group_volume_profile)

backgroundFill    = input.bool(false, 'Background Fill of Profile Range'             , inline ='BG', group = group_volume_profile)
backgroundColor   = input.color(color.new(#434651, 95), ''                     , inline ='BG', group = group_volume_profile)

show_dvp          = input.bool(false, 'Show Developing Profile', group = group_volume_profile)
show_previous     = input.bool(true, 'Show Previous POC, VAH, VAL', group = group_volume_profile)


// Definitions ---------------------------------------------------------------------------------- //
nzVolume          = nz(volume)

volumeStorageT    = array.new_float(profileLevels + 1, 0.)

var a_poc_lines   = array.new_line()

var x1            = 0
var x2            = 0
var levelAbovePoc = 0
var levelBelowPoc = 0
var pvtHigh1      = 0.
var pvtLow1       = 0.
var pvtLast       = ''
var pPOC          = 0.
var pvah          = 0.
var pval          = 0.


// Calculations --------------------------------------------------------------------------------- 

min_of_day      = hour * 60 + minute
intv            = 0
C_bar           = 0
bar_start       = false
period          = f_tfIsIntraday(periodic_tf)
daily_start     = regular_sess ? ta.barssince(session.isfirstbar_regular) : ta.barssince(session.isfirstbar)
weekly_start    = ta.barssince(ta.change(time("W")))
monthly_start   = ta.barssince(ta.change(time("1M")))
threeM_start    = ta.barssince(ta.change(time("3M")))
sixM_start      = ta.barssince(ta.change(time("6M")))
yearly_start    = ta.barssince(ta.change(time("12M")))
profileLength   = 0

if period == "Intraday"
    intv := int(f_tfResInMinutes(periodic_tf) / f_resInMinutes())
    C_bar := (min_of_day % intv)
    profileLength := intv
else if period == "Daily"
    C_bar := daily_start
    profileLength := daily_start[1]+1
else if period == "Weekly"
    C_bar := weekly_start
    profileLength := weekly_start[1]+1
else if period == "Monthly"
    C_bar := periodic_tf == "1M" ? monthly_start : periodic_tf == "3M" ? threeM_start : periodic_tf == "6M" ? sixM_start : periodic_tf == "12M" ? yearly_start : monthly_start
    profileLength := C_bar[1]

bar_start := C_bar == 0

proceed = bar_start

if proceed
    x1 := x2
    x2 := bar_index

[priceHighest, priceLowest, tradedVolume] = f_getHighLow(profileLength, proceed, 0)
priceStep = (priceHighest - priceLowest) / profileLevels

pvtHigh  = priceHighest
pvtLow   = priceLowest

if not na(pvtHigh)
    pvtHigh1 := pvtHigh
    pvtLast  := 'H'

if not na(pvtLow)
    pvtLow1  := pvtLow
    pvtLast  := 'L'

if proceed and nzVolume and priceStep > 0 and bar_index > profileLength and profileLength > 0

    for barIndexx = 1 to profileLength
        level = 0
        barIndex = barIndexx
        
        for priceLevel = priceLowest to priceHighest by priceStep
            if high[barIndex] >= priceLevel and low[barIndex] < priceLevel + priceStep
                array.set(volumeStorageT, level, array.get(volumeStorageT, level) + nzVolume[barIndex] * ((high[barIndex] - low[barIndex]) == 0 ? 1 : priceStep / (high[barIndex] - low[barIndex])) )
            level += 1

    pocLevel          = array.indexof(volumeStorageT, array.max(volumeStorageT))
    totalVolumeTraded = array.sum(volumeStorageT) * isValueArea
    valueArea         = array.get(volumeStorageT, pocLevel)
    levelAbovePoc    := pocLevel
    levelBelowPoc    := pocLevel
    
    while valueArea < totalVolumeTraded
        if levelBelowPoc == 0 and levelAbovePoc == profileLevels - 1
            break

        volumeAbovePoc = 0.
        if levelAbovePoc < profileLevels - 1 
            volumeAbovePoc := array.get(volumeStorageT, levelAbovePoc + 1)

        volumeBelowPoc = 0.
        if levelBelowPoc > 0
            volumeBelowPoc := array.get(volumeStorageT, levelBelowPoc - 1)
        
        if volumeBelowPoc == 0 and volumeAbovePoc == 0
            break
        
        if volumeAbovePoc >= volumeBelowPoc
            valueArea     += volumeAbovePoc
            levelAbovePoc += 1
        else
            valueArea     += volumeBelowPoc
            levelBelowPoc -= 1

    for level = 0 to profileLevels - 1
        if volumeProfile
            startBoxIndex = profilePlacement == 'Right' ? bar_index - int(array.get(volumeStorageT, level) / array.max(volumeStorageT) * profileLength * profileWidth)  : bar_index - profileLength
            endBoxIndex   = profilePlacement == 'Right' ? bar_index  :  startBoxIndex + int( array.get(volumeStorageT, level) / array.max(volumeStorageT) * profileLength * profileWidth)
            f_drawOnlyBoxX(startBoxIndex, priceLowest + (level + 0.1) * priceStep, endBoxIndex, priceLowest + (level + 0.9) * priceStep, level >= levelBelowPoc and level <= levelAbovePoc ? totalVolumeColor : vaVolumeColor, 1, line.style_solid)

    if backgroundFill
        f_drawOnlyBoxX(bar_index - profileLength, priceHighest, bar_index, priceLowest, backgroundColor, 1, line.style_dotted)

    if pointOfControl
        array.push(a_poc_lines, line.new(bar_index - profileLength, priceLowest + (array.indexof(volumeStorageT, array.max(volumeStorageT)) + 0.5) * priceStep, bar_index, priceLowest + (array.indexof(volumeStorageT, array.max(volumeStorageT)) + 0.5) * priceStep, color=pocColor, style= line.style_solid, width= pocWidth))
        // array.push(a_poc, box.new(bar_index - profileLength, priceLowest + (array.indexof(volumeStorageT, array.max(volumeStorageT)) + .40) * priceStep, bar_index, priceLowest + (array.indexof(volumeStorageT, array.max(volumeStorageT)) + .60) * priceStep, pocColor, bgcolor = pocColor ))
        
    vah = f_drawOnlyLineX(bar_index - profileLength, priceLowest + (levelAbovePoc + 1.00) * priceStep, bar_index, priceLowest + (levelAbovePoc + 1.00) * priceStep, xloc.bar_index, extend.none, valueAreaHigh ? vahColor : #00000000, line.style_solid, vaWidth)
    val = f_drawOnlyLineX(bar_index - profileLength, priceLowest + (levelBelowPoc + 0.00) * priceStep, bar_index, priceLowest + (levelBelowPoc + 0.00) * priceStep, xloc.bar_index, extend.none, valueAreaLow  ? valColor : #00000000, line.style_solid, vaWidth)

    if vaBackground
        linefill.new(vah, val, vaBackgroundColor)

    pPOC := priceLowest + (array.indexof(volumeStorageT, array.max(volumeStorageT)) + 0.5) * priceStep
    pvah := priceLowest + (levelAbovePoc + 1.00) * priceStep
    pval := priceLowest + (levelBelowPoc + 0.00) * priceStep

current_start = ta.barssince(bar_start)

var a_profileD    = array.new_box()
profileLength    := barstate.islast ? current_start : 1
priceHighest     := ta.highest(high, profileLength > 0 ? profileLength + 1 : 1)
priceLowest      := ta.lowest (low , profileLength > 0 ? profileLength + 1 : 1)
priceStep        := (priceHighest - priceLowest) / profileLevels
var pocLevel      = 0

[_, _, tradedVolume1] = f_getHighLow(profileLength, true, 0)
  
if barstate.islast and nzVolume and profileLength > 0 and priceStep > 0 and show_dvp

    if array.size(a_profileD) > 0
        for i = 0 to array.size(a_profileD) - 1
            box.delete(array.shift(a_profileD))

    for barIndex = 1 to profileLength
        level = 0

        for priceLevel = priceLowest to priceHighest by priceStep
            if high[barIndex] >= priceLevel and low[barIndex] < priceLevel + priceStep
                array.set(volumeStorageT, level, array.get(volumeStorageT, level) + nzVolume[barIndex] * ((high[barIndex] - low[barIndex]) == 0 ? 1 : priceStep / (high[barIndex] - low[barIndex])) )
            level += 1

    pocLevel          := array.indexof(volumeStorageT, array.max(volumeStorageT))
    totalVolumeTraded = array.sum(volumeStorageT) * isValueArea
    valueArea         = array.get(volumeStorageT, pocLevel)
    levelAbovePoc    := pocLevel
    levelBelowPoc    := pocLevel
    
    while valueArea < totalVolumeTraded
        if levelBelowPoc == 0 and levelAbovePoc == profileLevels - 1
            break

        volumeAbovePoc = 0.
        if levelAbovePoc < profileLevels - 1 
            volumeAbovePoc := array.get(volumeStorageT, levelAbovePoc + 1)

        volumeBelowPoc = 0.
        if levelBelowPoc > 0
            volumeBelowPoc := array.get(volumeStorageT, levelBelowPoc - 1)
            
        if volumeBelowPoc == 0 and volumeAbovePoc == 0
            break
        
        if volumeAbovePoc >= volumeBelowPoc
            valueArea     += volumeAbovePoc
            levelAbovePoc += 1
        else
            valueArea     += volumeBelowPoc
            levelBelowPoc -= 1

    for level = 0 to profileLevels - 1
        if volumeProfile
            startBoxIndex = profilePlacement == 'Right' ? bar_index - int(array.get(volumeStorageT, level) / array.max(volumeStorageT) * profileLength * profileWidth)  : bar_index - profileLength
            endBoxIndex   = profilePlacement == 'Right' ? bar_index  :  startBoxIndex + int( array.get(volumeStorageT, level) / array.max(volumeStorageT) * profileLength * profileWidth)
            array.push(a_profileD, box.new(startBoxIndex, priceLowest + (level + 0.1) * priceStep, endBoxIndex, priceLowest + (level + 0.9) * priceStep, level >= levelBelowPoc and level <= levelAbovePoc ? totalVolumeColor : vaVolumeColor, bgcolor = level >= levelBelowPoc and level <= levelAbovePoc ? totalVolumeColor : vaVolumeColor ))

    if backgroundFill
        array.push(a_profileD, box.new(bar_index - profileLength, priceHighest, bar_index, priceLowest, backgroundColor, bgcolor = backgroundColor ))

    if pointOfControl
        array.push(a_profileD, box.new(bar_index - profileLength, priceLowest + (pocLevel + .40) * priceStep, bar_index, priceLowest + (pocLevel + .60) * priceStep, pocColor, bgcolor = pocColor ))

    vah = f_drawLineX(bar_index - profileLength, priceLowest + (levelAbovePoc + 1.00) * priceStep, bar_index, priceLowest + (levelAbovePoc + 1.00) * priceStep, xloc.bar_index, extend.none, valueAreaHigh ? vahColor : #00000000, line.style_solid, vaWidth)
    val = f_drawLineX(bar_index - profileLength, priceLowest + (levelBelowPoc + 0.00) * priceStep, bar_index, priceLowest + (levelBelowPoc + 0.00) * priceStep, xloc.bar_index, extend.none, valueAreaLow  ? valColor : #00000000, line.style_solid, vaWidth)

    if vaBackground
        linefill.new(vah, val, vaBackgroundColor)
        
DPoC = priceLowest + (array.indexof(volumeStorageT, array.max(volumeStorageT)) + .50) * priceStep
DVAH = priceLowest + (levelAbovePoc + 1.00) * priceStep
DVAL = priceLowest + (levelBelowPoc + 0.00) * priceStep

var line DPoC_l = line.new(last_bar_index - profileLength, DPoC , last_bar_index, DPoC, color=pocColor, width = pocWidth)
var line DVAH_l = line.new(last_bar_index - profileLength, DVAH , last_bar_index, DVAH, color=vahColor, width = vaWidth)
var line DVAL_l = line.new(last_bar_index - profileLength, DVAL , last_bar_index, DVAL, color=valColor, width = vaWidth)

var line PPoC_l = line.new(last_bar_index - profileLength, pPOC , last_bar_index, pPOC, color=pocColor, style = line.style_dashed, width = pocWidth)
var line PVAH_l = line.new(last_bar_index - profileLength, pvah , last_bar_index, pvah, color=vahColor, style = line.style_dashed, width = vaWidth)
var line PVAL_l = line.new(last_bar_index - profileLength, pval , last_bar_index, pval, color=valColor, style = line.style_dashed, width = vaWidth)


if barstate.islast and show_dvp
    line.delete(DPoC_l)
    line.delete(DVAH_l)
    line.delete(DVAL_l)
    DPoC_l := line.new(last_bar_index - profileLength, DPoC , last_bar_index, DPoC, color=pocColor, width = pocWidth)
    DVAH_l := line.new(last_bar_index - profileLength, DVAH , last_bar_index, DVAH, color=vahColor, width = vaWidth)
    DVAL_l := line.new(last_bar_index - profileLength, DVAL , last_bar_index, DVAL, color=valColor, width = vaWidth)

if barstate.islast and show_previous
    line.delete(PPoC_l)
    line.delete(PVAH_l)
    line.delete(PVAL_l)
    PPoC_l := line.new(last_bar_index - profileLength, pPOC , last_bar_index, pPOC, color=pocColor, style = line.style_dotted, width = pocWidth)
    PVAH_l := line.new(last_bar_index - profileLength, pvah , last_bar_index, pvah, color=vahColor, style = line.style_dotted, width = vaWidth)
    PVAL_l := line.new(last_bar_index - profileLength, pval , last_bar_index, pval, color=valColor, style = line.style_dotted, width = vaWidth)
    
    if vaBackground
        linefill.new(PVAH_l, PVAL_l, color.new(vaBackgroundColor, 95))